/*
    Copyright 2008, 2009, 2010 Brightworks, Inc.

    This file is part of Language Mentor.

    Language Mentor is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Language Mentor is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Language Mentor.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.langcollab.languagementor.controller.download
{
    import com.brightworks.db.SQLiteQueryData_Insert;
    import com.brightworks.error.BwErrorEvent;
    import com.brightworks.util.Debug;
    import com.brightworks.util.IPercentCompleteReporter;
    import com.brightworks.util.ProgressBarManager;
    import com.brightworks.util.Utils_Math;
    import com.brightworks.vo.VO;
    import com.langcollab.languagementor.constant.Constant_Misc;
    import com.langcollab.languagementor.model.Callbacks;
    import com.langcollab.languagementor.model.MainModel;
    import com.langcollab.languagementor.vo.ContentProviderAccountVO;

    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.filesystem.File;
    import flash.system.Security;
    import flash.utils.Dictionary;

    /*
        This class thinks in terms of these concepts:
          - moduleId:
              - This is typically a URI minus its protocol, i.e. com.languagecollaborative.0000000001.
              - It is a folder name for the module's folder on the server
              - It is a file name, minus extension, for the module's zip file, which we download
              - It is a folder name once the downloaded zip file is unzipped and saved
                as a folder full of files

        _moduleStatusList is used to track the status of each moduleId
            each property name is a moduleId
            each property's value is set to one of the MODULE_STATUS constants.

        _moduleDataList holds the downloaded data
            each property name is a moduleId
            property values vary in format depending on what status that particular download has:
              - MODULE_STATUS_STARTED:     null
              - MODULE_STATUS_DOWNLOADED:  ByteArray
              - MODULE_STATUS_UNZIPPED:
              - MODULE_STATUS_COMPLETE:
    */

    public class DownloadProcessManager extends EventDispatcher implements IPercentCompleteReporter
    {
        private static const REPEATED_ATTEMPT_ALLOWED:int = 1;

        public var downloadFileExtension:String;
        public var downloadURLRoot:String;
        public var model:MainModel;
        public var publishedModuleVersionIdList:Array;
        public var saveFileFolderRoot:String;

        private var _callbacks:Callbacks;
        private var _index_downloadProcess_by_publishedModuleVersionId:Dictionary;
        private var _index_publishedModuleVersionId_by_downloadProcess:Dictionary;
        private var _moduleVersionDownloadAttemptCountInfo:Dictionary;
        private var _resultsReport:DownloadProcessManagerResultsReport;

        // --------------------------------------------
        //
        //
        //           Public Methods
        //
        //
        // --------------------------------------------

        public function DownloadProcessManager(model:MainModel)
        {
            this.model = model;
            super(null);
            _resultsReport = new DownloadProcessManagerResultsReport();
        }

        public function getPercentComplete():int 
        {
            var dp:DownloadProcess;
            var complete:int = 0;
            var total:int = 0;
            for each (dp in _index_downloadProcess_by_publishedModuleVersionId) {
                complete  += dp.getPercentComplete();
                total     += 100;
            }
            return Utils_Math.computePercentageInteger_RoundDown(complete, total);
        }

        public function start(callbacks:Callbacks):void 
        {
            Debug.info("DownloadProcessManager.start() - create/start dnld processes");
            _callbacks = callbacks;
            //ProgressBarManager.show("Downloading Modules", this);
            // Currently doing this in two places - here and DownloadCurrentRepositoryDataCommand
            Security.loadPolicyFile(downloadURLRoot + "/" + Constant_Misc.FILEPATHINFO_DOWNLOAD_PERMISSION_FILE_NAME);
            _index_downloadProcess_by_publishedModuleVersionId = new Dictionary();
            _index_publishedModuleVersionId_by_downloadProcess = new Dictionary();
            _moduleVersionDownloadAttemptCountInfo = new Dictionary();
            var publishedModuleVersionId:String;
            for each (publishedModuleVersionId in publishedModuleVersionIdList) 
            {
                _resultsReport.downloadProcessCount_Started++;
                startDownloadProcess(publishedModuleVersionId);
            }
        }

        // --------------------------------------------
        //
        //           Private Methods
        //
        // --------------------------------------------

        private function areAllDownloadProcessesCompleteOrFailed():Boolean 
        {
            var dp:DownloadProcess;
            for each (dp in _index_downloadProcess_by_publishedModuleVersionId) {
                if (!dp.isCompleteOrFailed()) {
                    return false;
                }
            }
            return true;
        }

        private function cleanupDownloadProcess(dp:DownloadProcess):void
        {
            dp.removeEventListener(Event.COMPLETE,              onDownloadCompleteEvent);
            dp.removeEventListener(BwErrorEvent.ERROR_GENERIC,  onDownloadFailedEvent);
            dp.dispose();
            var publishedModuleVersionId:String = _index_publishedModuleVersionId_by_downloadProcess[dp];
            _index_publishedModuleVersionId_by_downloadProcess[dp] = null;
            _index_downloadProcess_by_publishedModuleVersionId[publishedModuleVersionId] = null;
        }

        private function cleanupFailedDownloadProcesses():void 
        {
            /// This is called at the same time as saveCompleteModuleDataToDB().
            // _callbacks.result() should be called when both are finished.

            /// set _resultsReport.problemReport_FailureToDeleteUnsuccessfulDownloadFiles
            // if this process fails
        }

        private function getAttemptCountForModuleVersion(publishedModuleVersionId:String):int
        {
            if (_moduleVersionDownloadAttemptCountInfo.hasOwnProperty(publishedModuleVersionId))
            {
                return _moduleVersionDownloadAttemptCountInfo[publishedModuleVersionId];
            }
            else
            {
                return 0;
            }
        }

        private function getContentProviderAccountVO():VO 
        {
            var currentRepositoryXML:XML = model.currentRepositoryXML;
            var vo:ContentProviderAccountVO = new ContentProviderAccountVO();
            vo.contentProviderDisplayName  = currentRepositoryXML.contentProviderDisplayName[0].toString();
            vo.contentProviderId           = currentRepositoryXML.contentProviderId[0].toString();
            return vo;
        }

        private function getCurrentRepositoryXML():XML 
        {
            if (!model.currentRepositoryXML is XML) 
                Debug.error_Fatal("DownloadProcessManager.getCurrentRepositoryXML(): MainModel.currentRepositoryXML isn't an XML instance");
            return XML(model.currentRepositoryXML);
        }

        /*private function getResultsReport():Object
        {
            var result:Object = {};
            var dp:DownloadProcess;
            var downloadProcessInfo:Object;
            var errorInfo:Object;
            var publishedModuleVersionId:String;
            for (publishedModuleVersionId in _index_downloadProcess_by_publishedModuleVersionId)
            {
                dp = _index_downloadProcess_by_publishedModuleVersionId[publishedModuleVersionId];
                if (dp.status == DownloadProcess.STATUS_FAILED)
                {
                    downloadProcessInfo =	{
                        contentProviderId         : dp.contentProviderId,
                            downloadFileExtension     : dp.downloadFileExtension,
                            downloadURLRoot           : dp.downloadURLRoot,
                            errorData                 : dp.errorData,
                            publishedModuleVersionId  : dp.publishedModuleVersionId,
                            saveFileFolder            : dp.saveFileFolder,
                            status                    : dp.status
                    };
                    errorInfo =	{
                        downloadProcessInfo       : downloadProcessInfo,
                        errorType                 : ERROR_DOWNLOADPROCESS_FAILURE,
                        publishedModuleVersionId  : publishedModuleVersionId
                    };
                    result.push(errorInfo);
                }
            }
            if (_resultsReport.hasOwnProperty(ERROR_FAILED_TO_SAVE_DOWNLOADED_MODULE_DATA_TO_DB))
            {
                errorInfo =	{
                    errorType	: ERROR_FAILED_TO_SAVE_DOWNLOADED_MODULE_DATA_TO_DB,
                    errorInfo 	: _resultsReport[ERROR_FAILED_TO_SAVE_DOWNLOADED_MODULE_DATA_TO_DB]
                }
                result.push(errorInfo);
            }
            if (_resultsReport.hasOwnProperty(ERROR_FAILED_TO_DELETE_UNSUCCESSFUL_DOWNLOAD_FILES))
            {
                errorInfo =	{
                    errorType	: ERROR_FAILED_TO_DELETE_UNSUCCESSFUL_DOWNLOAD_FILES,
                    errorInfo 	: _resultsReport[ERROR_FAILED_TO_DELETE_UNSUCCESSFUL_DOWNLOAD_FILES]
                }
                result.push(errorInfo);
            }
            return result;
        }*/

        private function isModuleVersionEligibleForMoreDownloadAttempts(publishedModuleVersionId:String):Boolean
        {
            return (_moduleVersionDownloadAttemptCountInfo[publishedModuleVersionId] < REPEATED_ATTEMPT_ALLOWED);
        }

        private function iterateDownloadAttemptCount(publishedModuleVersionId:String):void
        {
            if (!_moduleVersionDownloadAttemptCountInfo.hasOwnProperty(publishedModuleVersionId))
            {
                _moduleVersionDownloadAttemptCountInfo[publishedModuleVersionId] = 0;
            }
            _moduleVersionDownloadAttemptCountInfo[publishedModuleVersionId]++;
        }

        private function onDownloadCompleteEvent(event:Event):void 
        {
            _resultsReport.downloadProcessCount_Succeeded++;
            onDownloadCompleteOrFailedEvent(event);
        }

        private function onDownloadCompleteOrFailedEvent(event:Event):void 
        {
            Debug.info("DownloadProcessManager.onDownloadCompleteOrFailedEvent()");
            if (areAllDownloadProcessesCompleteOrFailed()) 
            {
                Debug.info("DownloadProcessManager.onDownloadCompleteOrFailedEvent() - all DPs complete or failed");
                saveCompleteModuleDataToDB();
                cleanupFailedDownloadProcesses();
                    //ProgressBarManager.hide();
                    ////Alert currently not working in 4.5
            }
        }

        private function onDownloadFailedEvent(event:BwErrorEvent):void 
        {
            var dp:DownloadProcess = DownloadProcess(event.target);			
            var publishedModuleVersionId:String = _index_publishedModuleVersionId_by_downloadProcess[dp];
            _resultsReport.problemReportList_DownloadFailures.push(event.report);
            iterateDownloadAttemptCount(publishedModuleVersionId);
            var attemptCount:int = getAttemptCountForModuleVersion(publishedModuleVersionId);
            if (isModuleVersionEligibleForMoreDownloadAttempts(publishedModuleVersionId))
            {
                Debug.info("DownloadProcessManager.onDownloadFailedEvent() #" + 
                           attemptCount + " - " +
                           publishedModuleVersionId +
                           " - trying again");
                cleanupDownloadProcess(dp);
                startDownloadProcess(publishedModuleVersionId);
            }
            else
            {
                Debug.info("DownloadProcessManager.onDownloadFailedEvent() #" + 
                           attemptCount + " - " +
                           publishedModuleVersionId +
                           " - maximum allowed attempts, so download is now considered to have failed");
                _resultsReport.downloadProcessCount_Failed++;
                onDownloadCompleteOrFailedEvent(event);
            }
        }

        private function saveCompleteModuleDataToDB():void 
        {
            Debug.info("DownloadProcessManager.saveCompleteModuleDataToDB() - start");
            var dp:DownloadProcess;
            var publishedModuleVersionId:String;
            var queryData:SQLiteQueryData_Insert;
            var queryDataListForDB:Array = [];
            var queryDataVO:VO;
            var queryDataVOList:Array;
            queryData = new SQLiteQueryData_Insert();
            queryData.vo = getContentProviderAccountVO();
            queryDataListForDB.push(queryData);
            for (publishedModuleVersionId in _index_downloadProcess_by_publishedModuleVersionId) 
            {
                dp = _index_downloadProcess_by_publishedModuleVersionId[publishedModuleVersionId];
                if (dp.status == DownloadProcess.STATUS_COMPLETE)
                {
                    // Do nothing here, continue with save process for this module
                }
                else if (dp.status == DownloadProcess.STATUS_FAILED) 
                {
                    continue;
                }
                else {
                    //// implement user-friendly debug call
                    Debug.error_Fatal(["DownloadProcessManager.saveModuleDataToDB(): DownloadProcess's status is '" + dp.status, dp]);
                }
                queryDataVOList = dp.getAllVOs();
                if (!queryDataVOList)
                {
                    // We've already established the the DP's status is "complete" so this
                    // definitedly shouldn't be happening.
                    // _resultsReport.problemReport_FailureToExtractVODataFromDownloadProcess
                    //// TODO
                }
                for each (queryDataVO in queryDataVOList) {
                    queryData = new SQLiteQueryData_Insert();
                    queryData.vo = queryDataVO;
                    queryDataListForDB.push(queryData);
                }
            }
            var callbacks:Callbacks = new Callbacks(saveCompleteModuleDataToDB_resultHandler, saveCompleteModuleDataToDB_faultHandler);
            Debug.info("DownloadProcessManager.saveCompleteModuleDataToDB(): sending " + String(queryDataListForDB.length) + " queries into DB");
            model.dataManager.insertData(queryDataListForDB, callbacks);
        }

        private function saveCompleteModuleDataToDB_faultHandler(event:BwErrorEvent):void 
        {
            Debug.info("DownloadProcessManager.saveCompleteModuleDataToDB_faultHandler()");
            _resultsReport.problemReport_FailureToSaveDownloadedModuleDataToDB = event.report;
            _callbacks.result(_resultsReport);
        }

        private function saveCompleteModuleDataToDB_resultHandler(info:Object):void 
        {
            Debug.info("DownloadProcessManager.saveCompleteModuleDataToDB_resultHandler()");
            model.dataManager.initModuleData();
            _callbacks.result(_resultsReport);
        }

        private function startDownloadProcess(publishedModuleVersionId:String):void
        {
            var currentRepositoryXML:XML = getCurrentRepositoryXML();
            var contentProviderId:String = currentRepositoryXML.contentProviderId[0].toString();
            var dp:DownloadProcess = new DownloadProcess(model);
            dp.contentProviderId         = contentProviderId;
            dp.downloadFileExtension     = downloadFileExtension;
            dp.downloadURLRoot           = downloadURLRoot;
            dp.publishedModuleVersionId  = publishedModuleVersionId;
            dp.repeatedAttemptsAllowed   = REPEATED_ATTEMPT_ALLOWED;
            dp.saveFileFolder            = saveFileFolderRoot + File.separator + publishedModuleVersionId;
            dp.addEventListener(Event.COMPLETE,              onDownloadCompleteEvent);
            dp.addEventListener(BwErrorEvent.ERROR_GENERIC,  onDownloadFailedEvent);
            dp.start();
            _index_downloadProcess_by_publishedModuleVersionId[publishedModuleVersionId] = dp;
            _index_publishedModuleVersionId_by_downloadProcess[dp] = publishedModuleVersionId;
        }

    }
}


